package com.zq.server.api.service.impl;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.omg.CORBA.UserException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zq.common.config.Properties.WeChatProperties;
import com.zq.common.constant.*;
import com.zq.common.core.context.BaseContext;
import com.zq.common.core.domain.Result;
import com.zq.common.core.domain.entity.UserInfo;
import com.zq.common.core.domain.model.WeChatLoginUser;
import com.zq.common.core.redis.RedisCache;
import com.zq.common.exception.base.BaseException;
import com.zq.common.utils.RegexUtils;
import com.zq.common.utils.SecurityUtils;
import com.zq.common.utils.StringUtils;
import com.zq.common.utils.VerificationCodeUtil;
import com.zq.common.utils.http.HttpClientUtil;
import com.zq.server.api.dao.UserInfoMapper;
import com.zq.server.api.domain.dto.SendCodeDto;
import com.zq.server.api.domain.dto.UserPhoneLoginDto;
import com.zq.server.api.domain.dto.VerifyPhoneCodeDto;
import com.zq.server.api.domain.dto.userWxLoginDto;
import com.zq.server.api.domain.vo.UserPhoneLoginVo;
import com.zq.server.api.domain.vo.userWxLoginVo;
import com.zq.server.api.service.ApiUserInfoService;
import com.zq.server.core.sevcice.weChatTokenService;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Super
 */
@Service
@Slf4j
public class ApiUserInfoServiceImpl implements ApiUserInfoService {
    /**
     * 获取手机号的官方url //默认获取手机号请求
     */
    private static final String WX_PHONE_URL =
        "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=";
    /**
     * 属性 类型 必填 说明 grant_type string 是 填写 client_credential
     */
    // 微信接口指定url
    private static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
    private static final String WX_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
    private static final String WX_GRANT_TYPE = "client_credential";
    // 授权类型，此处只需填写 authorization_code
    private static final String GRANT_TYPE = "authorization_code";
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Autowired
    private WeChatProperties weChatProperties;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private weChatTokenService chatTokenService;

    /**
     * 微信登录。
     * 
     * @param userWxLoginDto 用户微信登录DTO
     * @return 用户信息
     */
    @Transactional(rollbackFor = BaseException.class)
    public Result weChatLogin(userWxLoginDto userWxLoginDto) {
        // http 调用微信接口 获得openid
        String openid = weChatGetOpenid(userWxLoginDto.getCode());
        if (StringUtils.isBlank(openid)) {
            log.error("openid is null", SystemConstant.LOGIN_FAILED);
            throw new BaseException("调用接口失败获取openid");
        }
        WeChatLoginUser weChatTokenUser = null;
        userWxLoginVo wxLoginVo = null;
        String token = null;
        UserInfo user = userInfoMapper.getUserInfoByOpenid(openid);
        if (StringUtils.isNull(user)) {
            // 新用户 完成自动注册
            UserInfo newUser = newUserLogin(openid);
            if (StringUtils.isNull(newUser)) {
                log.error("new user Login error", SystemConstant.LOGIN_FAILED);
                throw new BaseException(SystemConstant.LOGIN_FAILED);
            }
            user = newUser;
        }
        // 构建响应信息
        user.setId(userInfoMapper.getUserInfoByOpenid(openid).getId());
        weChatTokenUser = new WeChatLoginUser();
        weChatTokenUser.setUserId(user.getId());
        weChatTokenUser.setOpenid(user.getUOpenid());
        weChatTokenUser.setUser(user);
        token = chatTokenService.createToken(weChatTokenUser);
        wxLoginVo = userWxLoginVo.builder().userId(user.getId()).openId(user.getUOpenid()).token(token).build();
        return Result.success(wxLoginVo);
    }

    /**
     * 新用户注册
     *
     * @param openid
     * @return 用户信息
     */
    private UserInfo newUserLogin(String openid) {
        UserInfo user =
            UserInfo.builder().uOpenid(openid).updateTime(LocalDateTime.now()).uRegistertime(LocalDateTime.now())
                .uLastlogintime(LocalDateTime.now()).uMode(NumberConstants.ZERO_).uState(NumberConstants.ZERO_).build();
        userInfoMapper.save(user);
        return user;
    }

    /**
     * 获取用户手机号
     * 
     * @param userPhoneLoginDto
     * @return UserPhoneLoginVo
     */
    @Transactional
    public UserPhoneLoginVo getUserPhone(UserPhoneLoginDto userPhoneLoginDto) {
        try {
            // 参数校验
            if (userPhoneLoginDto == null || userPhoneLoginDto.getCode() == null) {
                log.error("UserPhoneLoginDto or code cannot be null");
                throw new IllegalArgumentException("UserPhoneLoginDto or code cannot be null");
            }
            Long userId = BaseContext.getCurrentId();
            boolean existsSessionToken = redisCache.hasKey(PhoneLoginConstant.SESSION_TOKEN);
            // token不存在 去调用微信接口查 然后放在redis 7500秒过期时间
            if (!existsSessionToken) {
                getWxSessionToken();
            }
            // 获取redis的sessionToken值
            Object sessionToken = redisCache.getCacheObject(PhoneLoginConstant.SESSION_TOKEN);
            // 获取手机号
            String phoneParam = getPhoneNumberByCode(sessionToken, userPhoneLoginDto.getCode());
            // 查找数据库是否有相同手机号
            if (userInfoMapper.getPhoneByPhoneNumber(phoneParam) > NumberConstants.ZERO) {
                log.error("手机号已处在");
                throw new BaseException(verifyPhoneCodeConstant.PHONE_EXIST);
            }
            // 更新用户信息
            updateUserInfo(userId, phoneParam);
            // 返回视图vo
            UserPhoneLoginVo userPhoneLoginVo = new UserPhoneLoginVo(phoneParam);
            return userPhoneLoginVo;
        } catch (Exception e) {
            // 处理异常
            log.error("Failed to get user phone number 添加手机号异常", e);
            throw new RuntimeException("添加手机号异常", e);
        }
    }

    /**
     * 手机号验证登录
     *
     * @param verifyPhoneCodeDto
     */
    public Result verifyPhoneCode(VerifyPhoneCodeDto verifyPhoneCodeDto) {
        String phoneRegular = "^1[3456789]\\d{9}$";
        // 参数校验
        if (!RegexUtils.Regex(phoneRegular, verifyPhoneCodeDto.getPhone())) {
            log.error("参数校验失败：手机号格式错误");
            throw new IllegalArgumentException("手机号格式错误");
        }
        // 判断验证码是否过期或不存在
        if (!redisCache.hasKey(verifyPhoneCodeDto.getPhone())) {
            return Result.error(verifyPhoneCodeConstant.EXPIRATION_DATE) ;
        }
        // 查询redis 中的code
        Object codeParam = redisCache.getCacheSet(verifyPhoneCodeDto.getPhone());
        if (verifyPhoneCodeDto.getCode().equals(codeParam.toString())) {
            // 判断redis 储存的验证码是否正确
            redisCache.deleteObject(verifyPhoneCodeDto.getPhone());
            updateUserInfo(BaseContext.getCurrentId(), verifyPhoneCodeDto.getPhone());
            return Result.success(verifyPhoneCodeConstant.SUCCESS);
        } else {
            // 验证码不正确
            return Result.error(verifyPhoneCodeConstant.INVALID_VERIFICATION_CODE);
        }
    }

    /**
     * 发送验证码
     *
     * @param sendCodeDto 发送验证码的参数对象
     * @return 发送验证码结果
     */
    public Result sendVerificationCode(SendCodeDto sendCodeDto) {
        // 查找数据库是否有相同手机号
        boolean phoneExists = userInfoMapper.getPhoneByPhoneNumber(sendCodeDto.getPhone()) > NumberConstants.ZERO;
        if (phoneExists) {
            log.error("手机号已存在");
            throw new BaseException(verifyPhoneCodeConstant.PHONE_EXIST);
        }
        // 判断用户是否多次点击
        if (redisCache.hasKey(sendCodeDto.getPhone())) {
            log.warn("用户多次点击获取验证码");
            return Result.error(verifyPhoneCodeConstant.SEND_CODE_TO_MANY);
        }
        // 生成四位数验证码 过期时间一分钟
        String producePhoneCode = VerificationCodeUtil.generateVerificationCode();
        log.info("生成的四位数验证码:{}", producePhoneCode);
        redisCache.setCacheObject(sendCodeDto.getPhone(), producePhoneCode, NumberConstants.ONE_, TimeUnit.MINUTES);
        // 判断验证码是否成功发送
        if (redisCache.hasKey(sendCodeDto.getPhone())) {
            log.info("验证码发送成功");
            return Result.success(SendCodeConstant.SEND_SUCCESS);
        } else {
            log.error("验证码发送失败");
            return Result.success(SendCodeConstant.SEND_ERROR);
        }
    }

    /**
     * 修改用户信息
     * @param user 要修改的用户信息
     * @return 操作是否成功
     * @throws UserException 用户异常
     */
    @Transactional(rollbackFor = BaseException.class)
    public Result updateUserInfo(UserInfo user) {
        if (StringUtils.isNull(user)) {
            throw new BaseException("User cannot be null");
        }
        try {
            user.setId(SecurityUtils.getWechatUserId());
            user.setUpdateTime(LocalDateTime.now());
            Integer update = userInfoMapper.update(user);
            if (update == NumberConstants.ONE_) {
                WeChatLoginUser weChatUser = SecurityUtils.getWeChatUser();
                weChatUser.setUser(userInfoMapper.getUserInfoByUserId(SecurityUtils.getWechatUserId()));
                chatTokenService.refreshToken(weChatUser);
                return Result.success();
            }
        } catch (BaseException e) {
            throw new BaseException(MysqlConstant.UPDATE_ERROR);
        }
        return Result.error("获取失败");
    }

    /**
     * 根据微信code获取手机号信息
     * @param sessionToken 微信会话令牌
     * @param code 微信授权code
     * @return 获取到的手机号信息
     * @throws IOException 当发生IO异常时抛出
     */
    private String getPhoneNumberByCode(Object sessionToken, String code) throws IOException {
        // 参数封装
        Map<String, String> params = new HashMap<>();
        params.put("code", code);
        // 发送HTTP POST请求获取手机号信息
        String responseJson = HttpClientUtil.doPost4Json(WX_PHONE_URL + sessionToken, params);
        // 解析返回的JSON数据
        JSONObject wxResponse = JSON.parseObject(responseJson);
        String phoneJson = wxResponse.getString(PhoneLoginConstant.WX_PHONE_INFO);
        // 判断手机号信息是否为空
        if (StringUtils.isBlank(phoneJson)) {
            log.error("GET_PHONE_MSG_ERROR = \"获取手机号信息失败：手机号信息为空\";");
            throw new IOException(PhoneLoginConstant.GET_PHONE_MSG_ERROR);
        }
        // 截取手机号信息
        return phoneJson.substring(16, 27);
    }

    /**
     * 更新用户手机号 getUserPhone
     * @param userId
     * @param phone
     */
    @Transactional
    public void updateUserInfo(Long userId, String phone) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(userId);
        userInfo.setUPhone(phone);
        userInfo.setULastlogintime(LocalDateTime.now());
        userInfo.setUpdateTime(LocalDateTime.now());
        userInfoMapper.update(userInfo);
    }

    /**
     * 调用微信官方api 获取openid接口
     * @param code
     * @return openid
     */
    public String weChatGetOpenid(String code) {
        // http 调用微信接口 获得openid
        Map<String, String> wxMap = new HashMap<>();
        wxMap.put("appid", weChatProperties.getAppid());
        wxMap.put("secret", weChatProperties.getSecret());
        wxMap.put("js_code", code);
        wxMap.put("grant_type", GRANT_TYPE);
        String json = HttpClientUtil.doGet(WX_LOGIN, wxMap);
        JSONObject jsonObject = JSON.parseObject(json);
        String openid = jsonObject.getString(JwtClaimsConstant.OPENID);
        return openid;
    }

    /**
     * 调用微信官方 获取微信token 密钥 有效期 7200s
     * 
     * @return sessionTokenParam
     */
    public String getWxSessionToken() {
        // http 调用微信接口 getWxSessionToken
        Map<String, String> param = new HashMap<>();
        param.put("appid", weChatProperties.getAppid());
        param.put("secret", weChatProperties.getSecret());
        param.put("grant_type", WX_GRANT_TYPE);
        String json = HttpClientUtil.doGet(WX_TOKEN_URL, param);
        JSONObject sessionToken = JSON.parseObject(json);
        String sessionTokenParam = sessionToken.getString(PhoneLoginConstant.SESSION_TOKEN);
        // 获取到的数据放在 redis 7200s
        redisCache.setCacheObject(PhoneLoginConstant.SESSION_TOKEN, sessionTokenParam,
            NumberConstants.ONE_HUNDRED_AND_TWENTY, TimeUnit.MINUTES);
        return sessionTokenParam;
    }
}
